﻿// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

namespace SnapObjects.Data
{
    public class DefaultValueConverter : ValueConverter<object, object>
    {
        private readonly Func<object, object> _fromProperty;
        private readonly Func<object, object> _toProperty;

        public DefaultValueConverter():this(typeof(object))
        {
        }

        public DefaultValueConverter(Type propertyType): this(propertyType, typeof(object))
        {
        }

        public DefaultValueConverter(Type propertyType, Type dbFileType)
        {
            this.PropertyType = propertyType;
            this.DbFieldType = dbFileType;

            _fromProperty = this.CreateFromPropertyFunc();
            _toProperty = this.CreateToPropertyFunc();
        }

        public override Type PropertyClrType => this.PropertyType;

        public override Type DbFieldClrType => this.DbFieldType;

        public override Expression<Func<object, object>> ConvertFromPropertyExpression => (m) => _fromProperty(m);

        public override Expression<Func<object, object>> ConvertToPropertyExpression => (m) => _toProperty(m);

        private Type PropertyType { get; set; }

        private Type DbFieldType { get; set; }

        internal override bool CanConvert(Type fromType, Type toType)
        {
            this.DbFieldType = fromType;
            this.PropertyType = toType;

            return base.CanConvert(fromType, toType);
        }

        private Func<object, object> CreateFromPropertyFunc()
        {
            object fromProperty(object propertyValue)
            {
                var value = this.ChangeValueType(propertyValue, this.DbFieldClrType);

                return value;
            };

            return fromProperty;
        }

        private Func<object, object> CreateToPropertyFunc()
        {
            object toProperty(object dbFieldValue)
            {
                var value = this.ChangeValueType(dbFieldValue, this.PropertyClrType);

                return value;
            };

            return toProperty;
        }

        private object ChangeValueType(object value, Type targeType)
        {
            if (value == null || value.GetType() == targeType)
            {
                return value;
            }

            if (ValueConvert.TryConvert(value, targeType, out var v))
            {
                return v;
            }

            var type = Nullable.GetUnderlyingType(targeType) ?? targeType;

            var typeCode = Type.GetTypeCode(type);

            if (typeCode >= TypeCode.Char && typeCode <= TypeCode.DateTime)
            {
                value = ValueConvert.Convert<string>(value);
            }

            try
            {
                value = ValueConvert.Convert(value, type);

                return value;
            }
            catch (InvalidCastException)
            {
                throw new InvalidCastException(String.Format(
                    Properties.Resource.INVALIDCAST_EXCEPTION, value.GetType().FullName, targeType.FullName));
            }
            catch
            {
                throw;
            }
        }
    }
}
